﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace HobSoft.SoccerBet2014.Domain.Common.Site
{
    using Domain.Configuration;
    using Domain.Schedule;
    using Domain.Models.Site;
    using Domain.Models;
    using Component;
    using Domain.DataService;
    using Schedule;
    using SiteContext;


    public abstract class SessionManagerBase : IBetSiteManagerService, IManagerSchedule
    {
        protected const int DEF_TIME_OUT = 60000;
        protected static string DEF_USER_AGENT = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)";
        protected static Encoding DEF_ENCODING = Encoding.UTF8;
        protected static string HTML_MIME = "text/html, application/xhtml+xml, */*";
        protected const int DEF_MAIN_REQUEST_TICK = 300;

        #region request settting
        private string _userAgent;
        public string UserAgent
        {
            get { return _userAgent ?? (_userAgent = DEF_USER_AGENT); }
            set { _userAgent = value; }
        }

        private int? _timeOut;
        public virtual int Timeout
        {
            get
            {
                return (_timeOut ?? (_timeOut = DEF_TIME_OUT)).Value;
            }
            set
            {
                _timeOut = value;
            }
        }

        private Encoding _encoding;
        public Encoding Encoding
        {
            get { return _encoding ?? (_encoding = DEF_ENCODING); }
            set { _encoding = value; }
        }
        #endregion

        #region repository&interface
        private ICookiesManager _cookiesManager;
        protected virtual ICookiesManager CookiesManager
        {
            get { return _cookiesManager; }
            set { _cookiesManager = value; }
        }

        private IBetGameDataService _repositoryService;

        protected IBetGameDataService RepositoryService
        {
            get { return _repositoryService; }
            set { _repositoryService = value; }
        }

        private ITimer _scheduleTimer;

        protected ITimer ScheduleTimer
        {
            get { return _scheduleTimer; }
            set { _scheduleTimer = value; }
        }

        private ScheduleConfiguration _scheduleConfig;

        public ScheduleConfiguration ScheduleConfig
        {
            get { return _scheduleConfig; }
            protected set { _scheduleConfig = value; }
        }

        protected SiteRequestLog scheduleRequestLog;

        public ISiteRequestLog ScheduleRequestLog
        {
            get { return scheduleRequestLog; }
        }


        #endregion

        #region state
        private int _mainRequestTick = DEF_MAIN_REQUEST_TICK;
        protected int MainRequestTick
        {
            get
            {
                return _mainRequestTick;
            }
            set
            {
                _mainRequestTick = value;
            }
        }
        protected abstract string SessionID { get; }

        public LoginState LoginState
        {
            get
            {
                return LoginInfo.State;
            }
            protected set
            {
                _loginInfo.State = value;
            }
        }

        protected LoginInfo _loginInfo;
        public ILoginInfo LoginInfo
        {
            get
            {
                return _loginInfo;
            }
        }

        public bool IsInitialize { get; protected set; }
        #endregion

        #region account
        public byte BookerID { get; protected set; }

        public string Account { get; protected set; }

        public string GlobalHost
        {
            get;
            protected set;
        }

        public string Host
        {
            get;
            protected set;
        }
        #endregion

        #region cotr and inititlize

        public SessionManagerBase(ICookiesManager cookiesManager, IBetGameDataService repositoryService)
        {
            CookiesManager = cookiesManager;
            RepositoryService = repositoryService;
        }

        public SessionManagerBase(ICookiesManager cookiesManager, IBetGameDataService repositoryService, string globalHost, byte bookerId, string account)
        {
            Initialize(cookiesManager, repositoryService, globalHost, bookerId, account);
        }

        public virtual void Initialize(string globalHost, byte bookerId, string account)
        {
            GlobalHost = globalHost;
            BookerID = bookerId;
            Account = account;
            _loginInfo = new LoginInfo(SessionID);
            LoginState = LoginState.Initialize;
            IsInitialize = true;
        }

        protected virtual void Initialize(ICookiesManager cookiesManager, IBetGameDataService repositoryService, string globalHost, byte bookerId, string account)
        {
            CookiesManager = cookiesManager;
            RepositoryService = repositoryService;
            Initialize(globalHost, bookerId, account);
        }
        #endregion

        #region request base
        protected virtual HttpWebRequest CreateRequest(Uri url, CookieContainer cookies, string type, string content, Action<HttpWebRequest> setRequest)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Timeout = Timeout;

            request.UserAgent = UserAgent;
            request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            request.KeepAlive = true;
            request.Headers.Add("Accept-Language", "zh-CN");
            request.Host = url.Host;
            request.Method = type;
            request.ServicePoint.Expect100Continue = false;
            if (setRequest != null)
                setRequest(request);

            if (url.Scheme.Equals("https"))
            {
                ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(RemoteCertificateCheckValidationResult);
                request.ProtocolVersion = HttpVersion.Version10;
            }
            if (cookies != null)
                request.CookieContainer = cookies;
            else
                request.CookieContainer = new CookieContainer();

            if (!string.IsNullOrEmpty(content) && type.Equals("POST", StringComparison.OrdinalIgnoreCase))
            {
                Stream reqStream = request.GetRequestStream();
                byte[] reqBuffer = Encoding.GetBytes(content);
                reqStream.Write(reqBuffer, 0, reqBuffer.Length);
                reqStream.Close();
            }
            return request;
        }

        protected virtual HttpWebRequest CreateRequest(Uri url, CookieContainer cookies, string accept, string referer, string content, string type)
        {
            return CreateRequest(url, cookies, type, content, rq =>
            {
                if (!string.IsNullOrEmpty(referer))
                    rq.Referer = referer;
                if (!string.IsNullOrEmpty(accept))
                    rq.Accept = accept;
            });
        }

        private static bool RemoteCertificateCheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, System.Net.Security.SslPolicyErrors errors)
        {
            return true; //总是接受  
        }

        protected virtual async Task<string> PostHtmlRequest(Uri url, CookieContainer cookies, string referer, string content, bool formUrlencoded, bool allowAutoRedirect)
        {
            HttpWebRequest request = CreateRequest(url, cookies, HTML_MIME, referer, content, "POST");
            request.AllowAutoRedirect = allowAutoRedirect;
            if (formUrlencoded)
                request.ContentType = "application/x-www-form-urlencoded";
            string resContent = string.Empty;
            try
            {
                var resTask = request.GetResponseAsync();
#if DEBUG
                Stopwatch watch = new Stopwatch();
                watch.Start();
#endif
                HttpWebResponse response = (HttpWebResponse)await resTask;
#if DEBUG
                watch.Stop();
                Debug.WriteLine("rq:{0}==>{1}", watch.ElapsedMilliseconds, url.ToString());
#endif
                if (resTask.Exception != null)
                    throw resTask.Exception;
                if (resTask.IsCanceled)
                    throw new Exception("发送请求被任务取消.");

                Stream resStream = response.GetResponseStream();
                CookiesManager.SetCookies(BookerID, Account, response.Cookies);
                StreamReader reader = new StreamReader(resStream, Encoding);
                resContent = reader.ReadToEnd();
                resStream.Close();
                response.Close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                request.Abort();
            }

            return resContent;
        }

        protected virtual async Task<string> PostHtmlRequest(Uri url, string referer, string content)
        {
            var cookies = CookiesManager.GetCookies(BookerID, Account, url);
            return await PostHtmlRequest(url, cookies, referer, content, true, true);
        }

        protected virtual async Task<string> PostHtmlRequest(Uri url, string referer, string content, bool allowAutoRedirect)
        {
            var cookies = CookiesManager.GetCookies(BookerID, Account, url);
            return await PostHtmlRequest(url, cookies, referer, content, true, allowAutoRedirect);
        }

        protected virtual async Task<string> PostHtmlRequest(Uri url, string content, bool allowAutoRedirect)
        {
            var cookies = CookiesManager.GetCookies(BookerID, Account, url);
            return await PostHtmlRequest(url, cookies, null, content, true, allowAutoRedirect);
        }

        protected virtual async Task<string> PostHtmlRequest(Uri url, string content)
        {
            var cookies = CookiesManager.GetCookies(BookerID, Account, url);
            return await PostHtmlRequest(url, cookies, null, content, true, true);
        }

        protected virtual async Task<string> GetHtmlRequest(Uri url, CookieContainer cookies, string referer, bool allowAutoRedirect)
        {
            var request = CreateRequest(url, cookies, HTML_MIME, referer, null, "GET");
            request.AllowAutoRedirect = allowAutoRedirect;
            string result = string.Empty;
            try
            {
                var resTask = request.GetResponseAsync();
#if DEBUG
                Stopwatch watch = new Stopwatch();
                watch.Start();
#endif
                HttpWebResponse response = (HttpWebResponse)await resTask;
#if DEBUG
                watch.Stop();
                Debug.WriteLine("rq:{0}==>{1}", watch.ElapsedMilliseconds, url.ToString());
#endif
                if (resTask.Exception != null)
                    throw resTask.Exception;
                Stream resStream = response.GetResponseStream();
                CookiesManager.SetCookies(BookerID, Account, response.Cookies);
                StreamReader reader = new StreamReader(resStream, Encoding);
                result = reader.ReadToEnd();
                resStream.Close();
                response.Close();
            }
            catch
            {
                throw;
            }
            finally
            {
                request.Abort();
            }
            return result;
        }
        protected virtual async Task<string> GetHtmlRequest(Uri url, CookieContainer cookies, string referer)
        {
            return await GetHtmlRequest(url, cookies, referer, true);
        }
        protected virtual async Task<string> GetHtmlRequest(Uri url, string referer, bool allowAutoRedirect)
        {
            var cookies = CookiesManager.GetCookies(BookerID, Account, url);
            return await GetHtmlRequest(url, cookies, referer, allowAutoRedirect);
        }

        protected virtual async Task<string> GetHtmlRequest(Uri url, string referer)
        {
            var cookies = CookiesManager.GetCookies(BookerID, Account, url);
            return await GetHtmlRequest(url, cookies, referer, true);
        }

        protected virtual async Task<string> GetHtmlRequest(Uri url)
        {
            var cookies = CookiesManager.GetCookies(BookerID, Account, url);
            return await GetHtmlRequest(url, cookies, null, false);
        }

        protected virtual string ReadResponseStream(HttpWebResponse response)
        {
            Stream resStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(resStream, Encoding);
            string result = reader.ReadToEnd();
            return result;
        }
        #endregion

        #region abstract method
        public abstract Task<bool> Start();

        /// <summary>
        /// 登陆博彩公司网站
        /// </summary>
        /// <param name="password">密码</param>
        /// <returns>true:登陆成功，false:登陆失败</returns>
        public virtual Task<bool> Signup(string password)
        {
            return Signup(password, null);
        }
        /// <summary>
        /// 登陆博彩公司网站
        /// </summary>
        /// <param name="password">密码</param>
        /// <param name="token">验证码，没有则为空</param>
        /// <returns>true:登陆成功，false:登陆失败</returns>
        public abstract Task<bool> Signup(string password, string token);
        /// <summary>
        /// 退出
        /// </summary>
        /// <returns></returns>
        public abstract Task<bool> Signout();
        /// <summary>
        /// 请求登陆验证码
        /// </summary>
        public abstract Task RequestSignupToken();
        /// <summary>
        /// 变更网站语言为中文
        /// </summary>
        public abstract Task ChangeLauguage();
        /// <summary>
        /// 维持网站心跳
        /// </summary>
        public abstract Task StartHeartbeat();
        /// <summary>
        /// 检测网站会话是否过期。
        /// </summary>
        /// <returns></returns>
        public abstract Task<bool> CheckSession();
        /// <summary>
        /// 请求今日数据首页页面
        /// </summary>
        /// <returns></returns>
        public abstract Task<string> RequestMainPage();

        /// <summary>
        /// 请求所有早盘赛事赔率
        /// </summary>
        /// <returns>赛事、赔率列表</returns>
        public abstract Task<IList<Game>> RequestEarlyMarketOdds();

        public abstract Task<bool> RequestUpdateLiveOdds();
        public abstract Task<bool> RequestUpdateTodayOdds();
        public abstract Task<IList<Game>> RequestLiveFullOdds();
        public abstract Task<IList<Game>> RequestTodayFullOdds();

        public abstract Task<BetTick> RequestTick(BetTick oldTick);
        public abstract Task<BetTick> RequestTick(string oddsId, OddsPrice price);

        public abstract Task<BettingResult> RequestBetting(BetTick tick, decimal money);
        public abstract Task<IList<BettingOrder>> RequestBettingMiniOrder();
        public abstract Task<IList<BettingOrder>> RequestBettingHisOrder();
        #endregion

        #region protected abstract
        /// <summary>
        /// 从html中检验登陆状态
        /// </summary>
        /// <param name="html">响应返回的htl</param>
        /// <returns>true:已经成功登陆，false:未登录（检查状态属性LoginState）</returns>
        /// <seealso cref="LoginState"/>
        protected abstract bool CheckLoginHtmlState(string html);

        protected abstract string WWWHost();
        #endregion

        #region public menthod
        protected Uri FormatUri(string format, params object[] args)
        {
            return new Uri(string.Format(format, args));
        }

        public virtual void RecoverContext()
        {
            Common.SiteContext.ContextManager.RecoverContext(BookerID, Account, DateTime.Now.AddMinutes(-5));
        }

        public virtual void SetSchedule(ITimer _timer, ScheduleConfiguration _config)
        {
            if (!ValidInitialize())
                throw new Exception("需要初始化程序");

            if (BookerID != _config.BmID)
                throw new ArgumentException("ScheduleConfiguration的BmID不匹配", "_config");
            ScheduleTimer = _timer;
            ScheduleConfig = _config;
            scheduleRequestLog = new SiteRequestLog(BookerID, Account);
            ScheduleTimer.Tick += ScheduleTimer_Tick;
            EnableTimerSchedule = false;
        }
        #endregion

        #region schedule
        public event Action<IBetSiteManagerService, BetSiteRequestType, string> OnScheduleError;
        public event Action<IBetSiteManagerService, BetSiteRequestType, byte, string> OnScheduleRequestBefore;
        public event Action<IBetSiteManagerService, BetSiteRequestType, byte, string> OnScheduleRequestOver;

        public bool EnableTimerSchedule
        {
            get;
            protected set;
        }
        public virtual void StopSchedule()
        {
            EnableTimerSchedule = false;
            ScheduleTimer.Stop();
        }
        public virtual void StartSchedule()
        {
            if (ScheduleTimer == null || ScheduleConfig == null)
                throw new Exception("需要初始化调度Timer和Config");
            if (!ValidSignup())
                throw new Exception("需要完成登陆");

            ScheduleTimer.Interval = ScheduleConfig.TimerInterval;
            ScheduleTimer.Start();
            SetMainRequestTick();
            EnableTimerSchedule = true;
        }

        private void ScheduleRequestLive()
        {
            var liveSpan = DateTime.Now - ScheduleRequestLog.LastLiveRequest;
            var todaySpan = DateTime.Now - ScheduleRequestLog.LastTodayRequest;

            if ((!liveSpan.HasValue && !todaySpan.HasValue)
                || (liveSpan.HasValue && todaySpan.HasValue
                    && liveSpan.Value.TotalSeconds > ScheduleConfig.LiveResetInterval
                    && todaySpan.Value.TotalSeconds > ScheduleConfig.TodayResetInterval))
            {
                RaiseOnScheduleRequestBefore(BetSiteRequestType.Live);
                scheduleRequestLog.LastLiveRequest = DateTime.Now;
                RequestLiveFullOdds().ContinueWith(rlt =>
                {
                    if (rlt.Exception != null)
                        RaiseOnScheduleError(BetSiteRequestType.Live, rlt.Exception.InnerException.Message);
                    RaiseOnScheduleRequestOver(BetSiteRequestType.Live);
                });
                RaiseOnScheduleRequestBefore(BetSiteRequestType.Today);
                scheduleRequestLog.LastTodayRequest = DateTime.Now;
                RequestTodayFullOdds().ContinueWith(rtt =>
                {
                    if (rtt.Exception != null)
                        RaiseOnScheduleError(BetSiteRequestType.Today, rtt.Exception.InnerException.Message);
                    RaiseOnScheduleRequestOver(BetSiteRequestType.Today);
                });
                return;
            }

            if (!liveSpan.HasValue || (liveSpan.HasValue && liveSpan.Value.TotalSeconds > ScheduleConfig.LiveResetInterval))
            {
                RaiseOnScheduleRequestBefore(BetSiteRequestType.Live);
                scheduleRequestLog.LastLiveRequest = DateTime.Now;
                RequestLiveFullOdds().ContinueWith(rlt =>
                {
                    if (rlt.Exception != null)
                        RaiseOnScheduleError(BetSiteRequestType.Live, rlt.Exception.InnerException.Message);
                    RaiseOnScheduleRequestOver(BetSiteRequestType.Live);
                });
            }
            else if (liveSpan.Value.TotalSeconds > ScheduleConfig.LiveCountdown)
            {
                RaiseOnScheduleRequestBefore(BetSiteRequestType.Live);
                scheduleRequestLog.LastLiveRequest = DateTime.Now;
                RequestUpdateLiveOdds().ContinueWith(rlt =>
                {
                    if (rlt.Exception != null)
                        RaiseOnScheduleError(BetSiteRequestType.Live, rlt.Exception.InnerException.Message);
                    RaiseOnScheduleRequestOver(BetSiteRequestType.Live);
                });
                StartHeartbeat();
            }

            if (!todaySpan.HasValue || (todaySpan.HasValue && todaySpan.Value.TotalSeconds > ScheduleConfig.TodayResetInterval))
            {
                RaiseOnScheduleRequestBefore(BetSiteRequestType.Today);
                scheduleRequestLog.LastTodayRequest = DateTime.Now;
                RequestTodayFullOdds().ContinueWith(rtt =>
                {
                    if (rtt.Exception != null)
                        RaiseOnScheduleError(BetSiteRequestType.Today, rtt.Exception.InnerException.Message);
                    RaiseOnScheduleRequestOver(BetSiteRequestType.Today);
                });
            }
            else if (todaySpan.Value.TotalSeconds > ScheduleConfig.TodayCountdown)
            {
                RaiseOnScheduleRequestBefore(BetSiteRequestType.Today);
                scheduleRequestLog.LastTodayRequest = DateTime.Now;
                RequestUpdateTodayOdds().ContinueWith(rtt =>
                {
                    if (rtt.Exception != null)
                        RaiseOnScheduleError(BetSiteRequestType.Today, rtt.Exception.InnerException.Message);
                    RaiseOnScheduleRequestOver(BetSiteRequestType.Today);
                });
            }
        }

        protected virtual void ScheduleTimer_Tick(object sender, EventArgs e)
        {
            ScheduleTimer.Stop();
            if (!ValidSignup())
            {
                EnableTimerSchedule = false;
                return;
            }

            var earlySpan = DateTime.Now - ScheduleRequestLog.LastEarlyRequest;
            var mainSpan = DateTime.Now - ScheduleRequestLog.LastMainRequest;

            if (!earlySpan.HasValue || earlySpan.Value.TotalSeconds > ScheduleConfig.EarlyCountdown)
            {
                scheduleRequestLog.LastEarlyRequest = DateTime.Now;
                RaiseOnScheduleRequestBefore(BetSiteRequestType.Early);
                RequestEarlyMarketOdds().ContinueWith(t =>
                {
                    if (t.Exception != null)
                        RaiseOnScheduleError(BetSiteRequestType.Early, t.Exception.InnerException.Message);
                    RaiseOnScheduleRequestOver(BetSiteRequestType.Early);
                });
            }

            if (!mainSpan.HasValue || mainSpan.Value.TotalSeconds > MainRequestTick)
            {
                SetMainRequestTick();
                scheduleRequestLog.LastMainRequest = DateTime.Now;
                RequestMainPage().ContinueWith(t =>
                {
                    if (t.Exception != null)
                        RaiseOnScheduleError(BetSiteRequestType.Early, t.Exception.InnerException.Message);
                    ScheduleRequestLive();

                    if (EnableTimerSchedule)
                        ScheduleTimer.Start();
                });
            }
            else
            {
                ScheduleRequestLive();

                if (EnableTimerSchedule)
                    ScheduleTimer.Start();
            }

        }

        protected void RaiseOnScheduleError(BetSiteRequestType type, string message)
        {
            if (OnScheduleError != null)
                OnScheduleError(this, type, message);
        }
        protected void RaiseOnScheduleRequestBefore(BetSiteRequestType type)
        {
            if (OnScheduleRequestBefore != null)
                OnScheduleRequestBefore(this, type, BookerID, Account);
        }
        protected void RaiseOnScheduleRequestOver(BetSiteRequestType type)
        {
            if (OnScheduleRequestOver != null)
                OnScheduleRequestOver(this, type, BookerID, Account);
        }
        #endregion

        #region state valid
        protected virtual void SetMainRequestTick()
        {
            MainRequestTick = DEF_MAIN_REQUEST_TICK;
        }

        /// <summary>
        /// 验证是否初始化
        /// </summary>
        /// <returns>true:已经初始化，false:未初始化</returns>
        protected virtual bool ValidInitialize()
        {
            return (BookerID > 0 && !string.IsNullOrEmpty(Account));
        }

        /// <summary>
        /// 验证是否登陆
        /// </summary>
        /// <returns>true:已经登陆，false:未登录</returns>
        protected virtual bool ValidSignup()
        {
            return (LoginInfo != null && LoginInfo.IsLogin);
        }

        /// <summary>
        /// 验证Session是否登陆在线，以便可以跳过登陆
        /// </summary>
        /// <returns>true:已经登陆，false:未登录</returns>
        public virtual bool ValidSessionOnline(DateTime lastUpdate)
        {
            var cookie = CookiesManager.GetHttpCookie(BookerID, Account, SessionID, lastUpdate, new string[] { WWWHost() });
            if (cookie == null || ValidHostIsWWW(cookie.Domain))
                return false;

            Host = cookie.Domain;
            _loginInfo = new LoginInfo(SessionID);
            _loginInfo.IsLogin = true;
            _loginInfo.ResetLogin(cookie.Value);
            return true;
        }

        /// <summary>
        /// 验证Host是否是www这样的未登录域
        /// </summary>
        /// <returns></returns>
        protected abstract bool ValidHostIsWWW(string host);

        #endregion

        #region debug
        [Conditional("DEBUG")]
        protected void DebugResponseCookies(HttpWebResponse response)
        {
            Debug.WriteLine("==========cookies===========");
            foreach (Cookie cookie in response.Cookies)
            {
                Debug.WriteLine(string.Format("{0}={1},{3:yy-MM-dd hh:mm:ss} {4}, {5}, {2}", cookie.Name, cookie.Value, cookie.Path, cookie.Expires, cookie.HttpOnly ? "HttpOnly" : "", cookie.Domain));
            }
        }

        [Conditional("DEBUG")]
        protected void DebugResponseResult(HttpWebResponse response)
        {
            Debug.WriteLine("==========text===========");
            byte[] resBuffer = new byte[1048576];
            Stream resStream = response.GetResponseStream();
            resStream.Read(resBuffer, 0, resBuffer.Length);
            resStream.Close();
            response.Close();

            string resContent = Encoding.GetString(resBuffer).Trim('\0');
            Debug.WriteLine(resContent);
        }
        #endregion


    }
}
